package org.yaac.client.ui;

import static com.google.common.base.Strings.isNullOrEmpty;

import java.util.HashMap;
import java.util.Map;

import javax.inject.Inject;

import org.yaac.client.YaacConstants;
import org.yaac.client.place.BlobPlace;
import org.yaac.client.place.BrowserPlace;
import org.yaac.client.place.EGQLPlace;
import org.yaac.client.place.EditorPlace;
import org.yaac.client.place.OverviewPlace;
import org.yaac.client.place.StatisticsPlace;


import com.google.gwt.cell.client.AbstractCell;
import com.google.gwt.cell.client.Cell;
import com.google.gwt.i18n.client.Constants;
import com.google.gwt.place.shared.Place;
import com.google.gwt.place.shared.PlaceController;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.view.client.ListDataProvider;
import com.google.gwt.view.client.SelectionChangeEvent;
import com.google.gwt.view.client.SingleSelectionModel;
import com.google.gwt.view.client.TreeViewModel;

/**
 * 
 * @author Max Zhu (thebbsky@gmail.com)
 * 
 */
public class MenuTreeViewModel implements TreeViewModel {

	/**
	 * The constants used in the menu.
	 */
	public static interface MenuConstants extends Constants {
		
		// category capability
		String categoryCapability();
		
		String capabilityOverview();
		
		// category datastore
		String categoryDatastore();

		String datastoreStatistics();
		
		String categoryBlobstore();

		String categoryMemcache();

		String categoryTaskQueue();
	}

	/**
	 * @author Max Zhu (thebbsky@gmail.com)
	 *
	 */
	private class MenuItem {
		
		/**
		 * url token to append
		 * 
		 * if url token is NOT null, then history event will be fired
		 */
		private final Place place;
		
		/**
		 * i18n display name
		 */
		private final String displayName;

		/**
		 * children items, if it's not empty, then user can expand this item
		 */
		private final ListDataProvider<MenuItem> children;
		
		/**
		 * @param urlToken
		 * @param displayName
		 */
		public MenuItem(Place place, String displayName) {
			super();
			this.place = place;
			this.displayName = displayName;
			this.children = new ListDataProvider<MenuItem>();
		}
		
		public boolean hasChild() {
			return !this.children.getList().isEmpty();
		}
	}
	
	/**
	 * @author Max Zhu (thebbsky@gmail.com)
	 *
	 */
	private class MenuCell extends AbstractCell<MenuItem> {
		@Override
		public void render(Cell.Context context, MenuItem value, SafeHtmlBuilder sb) {
			if (value != null) {
				sb.appendEscaped(value.displayName);
			}
		}
	}
	
	/**
	 * map used for main menu to re-select menu item on url change
	 */
	private final Map<String, MenuItem> urlTokenItemMap = new HashMap<String, MenuItem>();
	
	/**
	 * The top level categories
	 */
	private final ListDataProvider<MenuItem> categories;
	
	/**
	 * selection model handler for leaf categories
	 */
	private final SingleSelectionModel<MenuItem> selectionModel = new SingleSelectionModel<MenuItem>();
	
	/**
	 * 
	 */
	@Inject
	MenuTreeViewModel(YaacConstants constants, final PlaceController placeController) {
		categories = new ListDataProvider<MenuItem>();
		
		initializeTree(constants);
		
		// setup a listener of selection module to trigger place controller to switch places
		selectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() {
			@Override
			public void onSelectionChange(SelectionChangeEvent event) {
				MenuItem selected = selectionModel.getSelectedObject();
				if (selected != null && selected.place !=null) {					
					placeController.goTo(selected.place);
				}
			}
		});
	}
	
	/**
	 * init main menu
	 * 
	 * @param constants
	 */
	private void initializeTree(YaacConstants constants) {
		{
			// overview
			MenuItem c = new MenuItem(null, constants.categoryCapability());
			addChild(c, new OverviewPlace(), constants.capabilityOverview(), OverviewPlace.URL_TOKEN); //TODO : i18n
			categories.getList().add(c);
		}
		
		{
			// datastore
			MenuItem c = new MenuItem(null, constants.categoryDatastore());
			addChild(c, new StatisticsPlace(), constants.datastoreStatistics(), StatisticsPlace.URL_TOKEN); //TODO : i18n
			addChild(c, new BrowserPlace(), "Browser", BrowserPlace.URL_TOKEN);	//TODO : i18n
			// FIXME : this will override URL with empty token everytime there is a change on MenuItem
			addChild(c, new EditorPlace(""), "Editor", EditorPlace.URL_TOKEN);	//TODO : i18n
			addChild(c, new EGQLPlace(), "Extended GQL", EGQLPlace.URL_TOKEN);	//TODO : i18n
			categories.getList().add(c);
		}
		
		{
			// blobstore
			MenuItem c = new MenuItem(null, constants.categoryBlobstore());
			addChild(c, new BlobPlace(), "Viewer", BlobPlace.URL_TOKEN);	//TODO : i18n
			categories.getList().add(c);
		}
		
		{
			// memcache
			MenuItem c = new MenuItem(null, constants.categoryMemcache());
			categories.getList().add(c);
		}
		
		{
			// task queue
			MenuItem c = new MenuItem(null, constants.categoryTaskQueue());
			categories.getList().add(c);
		}
	}

	private void addChild(MenuItem parent, Place place,
			String displayName, String urlToken) {
		MenuItem child = new MenuItem(place, displayName);
		parent.children.getList().add(child);
		urlTokenItemMap.put(urlToken, child);
	}

	@Override
	public <T> NodeInfo<?> getNodeInfo(T value) {
		if (value == null) {
			// top level
			return new DefaultNodeInfo<MenuItem>(categories, new MenuCell());
		} else if (value instanceof MenuItem) {
			// on each category expand items
			return ((MenuItem) value).hasChild() ? 
					new DefaultNodeInfo<MenuItem>(((MenuItem) value).children, new MenuCell(), selectionModel, null) : null;
		}
		
		return null;
	}

	@Override
	public boolean isLeaf(Object value) {		
		if (value == null) {
			return false;
		} else if (value instanceof MenuItem) {
			return !((MenuItem)value).hasChild();
		} else {
			return false;
		}
	}

	public void updateSelection(String urlTokenPrefix) {
		if (isNullOrEmpty(urlTokenPrefix)) {
			return;
		}
		
		MenuItem item = urlTokenItemMap.get(urlTokenPrefix);
		selectionModel.setSelected(item, true);		
	}
}
